{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "b518b04cbfe0"
      },
      "source": [
        "##### Copyright 2020 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "906e07f6e562"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "daf323e33b84"
      },
      "source": [
        "# 처음부터 훈련 루프 작성하기"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2440f6e0c5ef"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td><a target=\"_blank\" href=\"https://www.tensorflow.org/guide/keras/writing_a_training_loop_from_scratch\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\">TensorFlow.org에서 보기</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ko/guide/keras/writing_a_training_loop_from_scratch.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\">Google Colab에서 실행</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ko/guide/keras/writing_a_training_loop_from_scratch.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\"> TensorFlow.org에서 보기</a></td>\n",
        "  <td><a href=\"https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ko/guide/keras/writing_a_training_loop_from_scratch.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\">노트북 다운로드</a></td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8d4ac441b1fc"
      },
      "source": [
        "## Setup"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ae2407ad926f"
      },
      "outputs": [],
      "source": [
        "import tensorflow as tf\n",
        "from tensorflow import keras\n",
        "from tensorflow.keras import layers\n",
        "import numpy as np"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0f5a253901f8"
      },
      "source": [
        "## 시작하기\n",
        "\n",
        "Keras는 기본 학습 및 평가 루프인 `fit()` 및 `evaluate()`를 제공합니다. 사용법은 [내장된 메서드를 이용한 훈련 및 평가](https://www.tensorflow.org/guide/keras/train_and_evaluate/) 가이드에서 다룹니다.\n",
        "\n",
        "`fit()`의 편리성을 이용하면서 해당 모델의 학습 알고리즘을 사용자 정의하려면(예를 들어, `fit()`를 사용하여 GAN을 훈련시킴), `Model` 클래스를 하위 클래스화하고 `fit()` 중에 반복적으로 호출되는 고유 `train_step()` 메서드를 구현할 수 있습니다. 이 내용은 <a href=\"https://www.tensorflow.org/guide/keras/customizing_what_happens_in_fit/\" data-md-type=\"link\">`fit()` 동작 사용자 정의하기</a> 가이드에서 다룹니다.\n",
        "\n",
        "이제 교육 및 평가에 대한 매우 낮은 수준의 제어를 원한다면 자체 교육 및 평가 루프를 처음부터 작성해야합니다. 이것이이 안내서의 내용입니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "f4f47351a3ec"
      },
      "source": [
        "## `GradientTape` 사용하기: 첫 번째 엔드 투 엔드 예제\n",
        "\n",
        "`GradientTape` 범위 내에서 모델을 호출하면 손실 값과 관련하여 레이어의 훈련 가능한 가중치 기울기를 가져올 수 있습니다. 최적화 프로그램 인스턴스를 사용하면 이러한 기울기를 이용해 이러한 변수를 업데이트할 수 있습니다(`model.trainable_weights`를 사용하여 가져올 수 있음).\n",
        "\n",
        "간단한 MNIST 모델을 생각해 보겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "aaa775ce7dab"
      },
      "outputs": [],
      "source": [
        "inputs = keras.Input(shape=(784,), name=\"digits\")\n",
        "x1 = layers.Dense(64, activation=\"relu\")(inputs)\n",
        "x2 = layers.Dense(64, activation=\"relu\")(x1)\n",
        "outputs = layers.Dense(10, name=\"predictions\")(x2)\n",
        "model = keras.Model(inputs=inputs, outputs=outputs)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "d8b02a5759cf"
      },
      "source": [
        "사용자 정의 훈련 루프와 함께 미니 배치 기울기를 사용해 이를 훈련시키겠습니다.\n",
        "\n",
        "우선 최적화 프로그램, 손실 함수 및 데이터세트가 필요합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "f2c6257b8d02"
      },
      "outputs": [],
      "source": [
        "# Instantiate an optimizer.\n",
        "optimizer = keras.optimizers.SGD(learning_rate=1e-3)\n",
        "# Instantiate a loss function.\n",
        "loss_fn = keras.losses.SparseCategoricalCrossentropy(from_logits=True)\n",
        "\n",
        "# Prepare the training dataset.\n",
        "batch_size = 64\n",
        "(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()\n",
        "x_train = np.reshape(x_train, (-1, 784))\n",
        "x_test = np.reshape(x_test, (-1, 784))\n",
        "\n",
        "# Reserve 10,000 samples for validation.\n",
        "x_val = x_train[-10000:]\n",
        "y_val = y_train[-10000:]\n",
        "x_train = x_train[:-10000]\n",
        "y_train = y_train[:-10000]\n",
        "\n",
        "# Prepare the training dataset.\n",
        "train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))\n",
        "train_dataset = train_dataset.shuffle(buffer_size=1024).batch(batch_size)\n",
        "\n",
        "# Prepare the validation dataset.\n",
        "val_dataset = tf.data.Dataset.from_tensor_slices((x_val, y_val))\n",
        "val_dataset = val_dataset.batch(batch_size)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "5c30285b1a2e"
      },
      "source": [
        "사용자 정의 훈련 루프\n",
        "\n",
        "- 우리는 시대를 반복하는 `for` 루프를 엽니 다.\n",
        "- 각 시대마다 데이터 집합을 반복적으로 처리하는 `for` 루프를 엽니 다.\n",
        "- 각 배치마다 `GradientTape()` 범위를 엽니 다\n",
        "- 이 범위 내에서 모델을 호출하고(순방향 전달) 손실을 계산합니다.\n",
        "- 스코프 외부에서 손실과 관련하여 모델 가중치의 기울기를 검색합니다.\n",
        "- 마지막으로, 그라디언트를 기반으로 모델의 가중치를 업데이트하기 위해 옵티 마이저를 사용합니다"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "5bf4c10ceb50"
      },
      "outputs": [],
      "source": [
        "epochs = 2\n",
        "for epoch in range(epochs):\n",
        "    print(\"\\nStart of epoch %d\" % (epoch,))\n",
        "\n",
        "    # Iterate over the batches of the dataset.\n",
        "    for step, (x_batch_train, y_batch_train) in enumerate(train_dataset):\n",
        "\n",
        "        # Open a GradientTape to record the operations run\n",
        "        # during the forward pass, which enables auto-differentiation.\n",
        "        with tf.GradientTape() as tape:\n",
        "\n",
        "            # Run the forward pass of the layer.\n",
        "            # The operations that the layer applies\n",
        "            # to its inputs are going to be recorded\n",
        "            # on the GradientTape.\n",
        "            logits = model(x_batch_train, training=True)  # Logits for this minibatch\n",
        "\n",
        "            # Compute the loss value for this minibatch.\n",
        "            loss_value = loss_fn(y_batch_train, logits)\n",
        "\n",
        "        # Use the gradient tape to automatically retrieve\n",
        "        # the gradients of the trainable variables with respect to the loss.\n",
        "        grads = tape.gradient(loss_value, model.trainable_weights)\n",
        "\n",
        "        # Run one step of gradient descent by updating\n",
        "        # the value of the variables to minimize the loss.\n",
        "        optimizer.apply_gradients(zip(grads, model.trainable_weights))\n",
        "\n",
        "        # Log every 200 batches.\n",
        "        if step % 200 == 0:\n",
        "            print(\n",
        "                \"Training loss (for one batch) at step %d: %.4f\"\n",
        "                % (step, float(loss_value))\n",
        "            )\n",
        "            print(\"Seen so far: %s samples\" % ((step + 1) * batch_size))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "d600076b7be0"
      },
      "source": [
        "## 낮은 수준의 메트릭 처리\n",
        "\n",
        "이 기본 루프에 메트릭 모니터링을 추가하겠습니다.\n",
        "\n",
        "처음부터 작성한 이러한 훈련 루프에서 내장 메트릭(또는 사용자가 작성한 사용자 정의 메트릭)을 쉽게 재사용할 수 있습니다. 흐름은 다음과 같습니다.\n",
        "\n",
        "- 루프가 시작되는 부분에 메트릭 인스턴스화\n",
        "- 각 배치 끝에서 `metric.update_state()` 호출\n",
        "- 메트릭의 현재 값을 표시해야 할 때 `metric.result()` 호출\n",
        "- 메트릭의 상태를 지워야 할 때(일반적으로 epoch의 끝에서) `metric.reset_states()` 호출\n",
        "\n",
        "이러한 지식을 바탕으로 각 epoch가 끝날 때 유효성 검사 데이터에 대해 `SparseCategoricalAccuracy`를 계산하겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "2602509b16c7"
      },
      "outputs": [],
      "source": [
        "# Get model\n",
        "inputs = keras.Input(shape=(784,), name=\"digits\")\n",
        "x = layers.Dense(64, activation=\"relu\", name=\"dense_1\")(inputs)\n",
        "x = layers.Dense(64, activation=\"relu\", name=\"dense_2\")(x)\n",
        "outputs = layers.Dense(10, name=\"predictions\")(x)\n",
        "model = keras.Model(inputs=inputs, outputs=outputs)\n",
        "\n",
        "# Instantiate an optimizer to train the model.\n",
        "optimizer = keras.optimizers.SGD(learning_rate=1e-3)\n",
        "# Instantiate a loss function.\n",
        "loss_fn = keras.losses.SparseCategoricalCrossentropy(from_logits=True)\n",
        "\n",
        "# Prepare the metrics.\n",
        "train_acc_metric = keras.metrics.SparseCategoricalAccuracy()\n",
        "val_acc_metric = keras.metrics.SparseCategoricalAccuracy()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "9111a5cc87dc"
      },
      "source": [
        "훈련 및 평가 루프는 다음과 같습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "654e2311dbff"
      },
      "outputs": [],
      "source": [
        "import time\n",
        "\n",
        "epochs = 2\n",
        "for epoch in range(epochs):\n",
        "    print(\"\\nStart of epoch %d\" % (epoch,))\n",
        "    start_time = time.time()\n",
        "\n",
        "    # Iterate over the batches of the dataset.\n",
        "    for step, (x_batch_train, y_batch_train) in enumerate(train_dataset):\n",
        "        with tf.GradientTape() as tape:\n",
        "            logits = model(x_batch_train, training=True)\n",
        "            loss_value = loss_fn(y_batch_train, logits)\n",
        "        grads = tape.gradient(loss_value, model.trainable_weights)\n",
        "        optimizer.apply_gradients(zip(grads, model.trainable_weights))\n",
        "\n",
        "        # Update training metric.\n",
        "        train_acc_metric.update_state(y_batch_train, logits)\n",
        "\n",
        "        # Log every 200 batches.\n",
        "        if step % 200 == 0:\n",
        "            print(\n",
        "                \"Training loss (for one batch) at step %d: %.4f\"\n",
        "                % (step, float(loss_value))\n",
        "            )\n",
        "            print(\"Seen so far: %d samples\" % ((step + 1) * batch_size))\n",
        "\n",
        "    # Display metrics at the end of each epoch.\n",
        "    train_acc = train_acc_metric.result()\n",
        "    print(\"Training acc over epoch: %.4f\" % (float(train_acc),))\n",
        "\n",
        "    # Reset training metrics at the end of each epoch\n",
        "    train_acc_metric.reset_states()\n",
        "\n",
        "    # Run a validation loop at the end of each epoch.\n",
        "    for x_batch_val, y_batch_val in val_dataset:\n",
        "        val_logits = model(x_batch_val, training=False)\n",
        "        # Update val metrics\n",
        "        val_acc_metric.update_state(y_batch_val, val_logits)\n",
        "    val_acc = val_acc_metric.result()\n",
        "    val_acc_metric.reset_states()\n",
        "    print(\"Validation acc: %.4f\" % (float(val_acc),))\n",
        "    print(\"Time taken: %.2fs\" % (time.time() - start_time))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "940d8d9fae83"
      },
      "source": [
        "## `tf.function`을 이용해 훈련 단계의 속도 높이기\n",
        "\n",
        "TensorFlow 2.0의 기본 런타임은 [즉시 실행형](https://www.tensorflow.org/guide/eager)입니다. 따라서 위의 훈련 루프는 즉시 실행됩니다.\n",
        "\n",
        "이것은 디버깅에는 유용하지만 그래프 컴파일에는 확실한 성능 이점이 있습니다. 계산을 정적 그래프로 정의하면 프레임 워크에서 전체 성능 최적화를 적용 할 수 있습니다. 프레임 워크가 다음 작업을 알지 못하고 탐욕스럽게 하나의 작업을 실행하도록 제한되어 있으면 불가능합니다.\n",
        "\n",
        "텐서를 입력으로 사용하는 모든 함수를 정적 그래프로 컴파일 할 수 있습니다. 다음과 같이 `@tf.function` 데코레이터를 추가하십시오."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "fdacc2d48ade"
      },
      "outputs": [],
      "source": [
        "@tf.function\n",
        "def train_step(x, y):\n",
        "    with tf.GradientTape() as tape:\n",
        "        logits = model(x, training=True)\n",
        "        loss_value = loss_fn(y, logits)\n",
        "    grads = tape.gradient(loss_value, model.trainable_weights)\n",
        "    optimizer.apply_gradients(zip(grads, model.trainable_weights))\n",
        "    train_acc_metric.update_state(y, logits)\n",
        "    return loss_value\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ab61b0bf3126"
      },
      "source": [
        "평가 단계에도 동일한 작업을 수행합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "da4828fd8ef7"
      },
      "outputs": [],
      "source": [
        "@tf.function\n",
        "def test_step(x, y):\n",
        "    val_logits = model(x, training=False)\n",
        "    val_acc_metric.update_state(y, val_logits)\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "d552377968f1"
      },
      "source": [
        "이제 이 컴파일된 훈련 단계를 이용해 훈련 루프를 다시 실행해 보겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "d69d73c94e44"
      },
      "outputs": [],
      "source": [
        "import time\n",
        "\n",
        "epochs = 2\n",
        "for epoch in range(epochs):\n",
        "    print(\"\\nStart of epoch %d\" % (epoch,))\n",
        "    start_time = time.time()\n",
        "\n",
        "    # Iterate over the batches of the dataset.\n",
        "    for step, (x_batch_train, y_batch_train) in enumerate(train_dataset):\n",
        "        loss_value = train_step(x_batch_train, y_batch_train)\n",
        "\n",
        "        # Log every 200 batches.\n",
        "        if step % 200 == 0:\n",
        "            print(\n",
        "                \"Training loss (for one batch) at step %d: %.4f\"\n",
        "                % (step, float(loss_value))\n",
        "            )\n",
        "            print(\"Seen so far: %d samples\" % ((step + 1) * batch_size))\n",
        "\n",
        "    # Display metrics at the end of each epoch.\n",
        "    train_acc = train_acc_metric.result()\n",
        "    print(\"Training acc over epoch: %.4f\" % (float(train_acc),))\n",
        "\n",
        "    # Reset training metrics at the end of each epoch\n",
        "    train_acc_metric.reset_states()\n",
        "\n",
        "    # Run a validation loop at the end of each epoch.\n",
        "    for x_batch_val, y_batch_val in val_dataset:\n",
        "        test_step(x_batch_val, y_batch_val)\n",
        "\n",
        "    val_acc = val_acc_metric.result()\n",
        "    val_acc_metric.reset_states()\n",
        "    print(\"Validation acc: %.4f\" % (float(val_acc),))\n",
        "    print(\"Time taken: %.2fs\" % (time.time() - start_time))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8977d77a8095"
      },
      "source": [
        "훨씬 빠르지 않나요?"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "b5b5a54d339a"
      },
      "source": [
        "## 모델에 의해 추적되는 손실의 저수준 처리\n",
        "\n",
        "레이어 및 모델은 `self.add_loss(value)` 를 호출하는 레이어에 의해 순방향 전달 중에 생성되는 모든 손실을 재귀적으로 추적합니다. 스칼라 손실 값의 결과 목록은 순방향 전달이 끝날 때 `model.losses` 속성을 통해 이용할 수 있습니다.\n",
        "\n",
        "이러한 손실 구성 요소를 사용하려면 해당 구성 요소를 합산하여 교육 단계의 주요 손실에 추가해야합니다.\n",
        "\n",
        "이 계층을 고려하면 활동 정규화 손실이 발생합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "4ec7c4b16596"
      },
      "outputs": [],
      "source": [
        "class ActivityRegularizationLayer(layers.Layer):\n",
        "    def call(self, inputs):\n",
        "        self.add_loss(1e-2 * tf.reduce_sum(inputs))\n",
        "        return inputs\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6b12260b8bf2"
      },
      "source": [
        "이것을 사용하는 정말 간단한 모델을 만들어 보겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "57afe49e6b93"
      },
      "outputs": [],
      "source": [
        "inputs = keras.Input(shape=(784,), name=\"digits\")\n",
        "x = layers.Dense(64, activation=\"relu\")(inputs)\n",
        "# Insert activity regularization as a layer\n",
        "x = ActivityRegularizationLayer()(x)\n",
        "x = layers.Dense(64, activation=\"relu\")(x)\n",
        "outputs = layers.Dense(10, name=\"predictions\")(x)\n",
        "\n",
        "model = keras.Model(inputs=inputs, outputs=outputs)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "aadb58115c13"
      },
      "source": [
        "훈련 단계는 다음과 같습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "cf674776a0d2"
      },
      "outputs": [],
      "source": [
        "@tf.function\n",
        "def train_step(x, y):\n",
        "    with tf.GradientTape() as tape:\n",
        "        logits = model(x, training=True)\n",
        "        loss_value = loss_fn(y, logits)\n",
        "        # Add any extra losses created during the forward pass.\n",
        "        loss_value += sum(model.losses)\n",
        "    grads = tape.gradient(loss_value, model.trainable_weights)\n",
        "    optimizer.apply_gradients(zip(grads, model.trainable_weights))\n",
        "    train_acc_metric.update_state(y, logits)\n",
        "    return loss_value\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0af04732fe78"
      },
      "source": [
        "##  요약\n",
        "\n",
        "이제 내장된 훈련 루프를 사용하고 처음부터 고유한 루프를 작성하는 방법에 대해 알아야 할 모든 내용을 살펴보았습니다.\n",
        "\n",
        "결론적으로, 다음은이 가이드에서 배운 모든 것, 즉 MNIST 숫자에 대해 훈련 된 DCGAN을 하나로 묶는 간단한 종단 간 예제입니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "9fb325331a1e"
      },
      "source": [
        "## 엔드-투-엔드 예제: 처음부터 GAN 훈련 루프 만들기\n",
        "\n",
        "아마도 GAN(Generative Adversarial Networks)에 익숙할 것으로 생각합니다. GAN은 이미지의 훈련 데이터세트의 잠재 분포(이미지의 \"잠재적 공간\")를 학습함으로써 거의 실제처럼 보이는 새로운 이미지를 생성할 수 있습니다.\n",
        "\n",
        "GAN은 잠재적 공간의 포인트를 이미지 공간의 포인트에 매핑하는 \"생성기(generator)\" 모델과 실제 이미지(훈련 데이터세트로 만들어짐)와 가짜 이미지(생성기 네트워크의 출력)의 차이를 구분할 수 있는 분류기인 \"판별자(discriminator)\" 모델의 두 부분으로 이루어집니다.\n",
        "\n",
        "A GAN training loop looks like this:\n",
        "\n",
        "1. 차별자를 훈련 시키십시오.\n",
        "\n",
        "- 잠재 공간에서 임의의 지점을 일괄 적으로 샘플링합니다.\n",
        "- \"생성기\" 모델을 통해 포인트를 가짜 이미지로 바꿉니다.\n",
        "- 실제 이미지 배치를 가져와 생성된 이미지와 결합합니다.\n",
        "- 생성 된 이미지와 실제 이미지를 분류하기 위해 \"차별 자\"모델을 훈련시킵니다.\n",
        "\n",
        "1. 생성기를 훈련시킵니다.\n",
        "\n",
        "- 잠재 공간에서 임의의 포인트를 샘플링합니다.\n",
        "- \"발전기\"네트워크를 통해 포인트를 가짜 이미지로 바꾸십시오.\n",
        "- 실제 이미지 배치를 가져와 생성된 이미지와 결합합니다.\n",
        "- \"생성기\" 모델을 훈련시켜 판별자를 \"속이고\" 가짜 이미지를 실제 이미지로 분류합니다.\n",
        "\n",
        "GAN 작동 방식에 대한 자세한 개요는 [Python을 사용한 딥 러닝](https://www.manning.com/books/deep-learning-with-python)을 참조하세요.\n",
        "\n",
        "이 훈련 루프를 구현해 보겠습니다. 먼저 가짜 숫자와 진짜 숫자를 분류하도록 의도된 판별자를 만듭니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "fabf9cef3400"
      },
      "outputs": [],
      "source": [
        "discriminator = keras.Sequential(\n",
        "    [\n",
        "        keras.Input(shape=(28, 28, 1)),\n",
        "        layers.Conv2D(64, (3, 3), strides=(2, 2), padding=\"same\"),\n",
        "        layers.LeakyReLU(alpha=0.2),\n",
        "        layers.Conv2D(128, (3, 3), strides=(2, 2), padding=\"same\"),\n",
        "        layers.LeakyReLU(alpha=0.2),\n",
        "        layers.GlobalMaxPooling2D(),\n",
        "        layers.Dense(1),\n",
        "    ],\n",
        "    name=\"discriminator\",\n",
        ")\n",
        "discriminator.summary()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "73396eb6daf9"
      },
      "source": [
        "그런 다음, 잠재 벡터를 형상 `(28, 28, 1)`(MNIST 숫자를 나타냄)의 출력으로 바꾸는 생성기 네트워크를 만듭니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "821d203bfb3e"
      },
      "outputs": [],
      "source": [
        "latent_dim = 128\n",
        "\n",
        "generator = keras.Sequential(\n",
        "    [\n",
        "        keras.Input(shape=(latent_dim,)),\n",
        "        # We want to generate 128 coefficients to reshape into a 7x7x128 map\n",
        "        layers.Dense(7 * 7 * 128),\n",
        "        layers.LeakyReLU(alpha=0.2),\n",
        "        layers.Reshape((7, 7, 128)),\n",
        "        layers.Conv2DTranspose(128, (4, 4), strides=(2, 2), padding=\"same\"),\n",
        "        layers.LeakyReLU(alpha=0.2),\n",
        "        layers.Conv2DTranspose(128, (4, 4), strides=(2, 2), padding=\"same\"),\n",
        "        layers.LeakyReLU(alpha=0.2),\n",
        "        layers.Conv2D(1, (7, 7), padding=\"same\", activation=\"sigmoid\"),\n",
        "    ],\n",
        "    name=\"generator\",\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "f0d6d54a78a0"
      },
      "source": [
        "핵심 비트는 훈련 루프입니다. 보시다시피 매우 간단합니다. 훈련 단계 기능은 17 줄만 사용합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "3a11c875142e"
      },
      "outputs": [],
      "source": [
        "# Instantiate one optimizer for the discriminator and another for the generator.\n",
        "d_optimizer = keras.optimizers.Adam(learning_rate=0.0003)\n",
        "g_optimizer = keras.optimizers.Adam(learning_rate=0.0004)\n",
        "\n",
        "# Instantiate a loss function.\n",
        "loss_fn = keras.losses.BinaryCrossentropy(from_logits=True)\n",
        "\n",
        "\n",
        "@tf.function\n",
        "def train_step(real_images):\n",
        "    # Sample random points in the latent space\n",
        "    random_latent_vectors = tf.random.normal(shape=(batch_size, latent_dim))\n",
        "    # Decode them to fake images\n",
        "    generated_images = generator(random_latent_vectors)\n",
        "    # Combine them with real images\n",
        "    combined_images = tf.concat([generated_images, real_images], axis=0)\n",
        "\n",
        "    # Assemble labels discriminating real from fake images\n",
        "    labels = tf.concat(\n",
        "        [tf.ones((batch_size, 1)), tf.zeros((real_images.shape[0], 1))], axis=0\n",
        "    )\n",
        "    # Add random noise to the labels - important trick!\n",
        "    labels += 0.05 * tf.random.uniform(labels.shape)\n",
        "\n",
        "    # Train the discriminator\n",
        "    with tf.GradientTape() as tape:\n",
        "        predictions = discriminator(combined_images)\n",
        "        d_loss = loss_fn(labels, predictions)\n",
        "    grads = tape.gradient(d_loss, discriminator.trainable_weights)\n",
        "    d_optimizer.apply_gradients(zip(grads, discriminator.trainable_weights))\n",
        "\n",
        "    # Sample random points in the latent space\n",
        "    random_latent_vectors = tf.random.normal(shape=(batch_size, latent_dim))\n",
        "    # Assemble labels that say \"all real images\"\n",
        "    misleading_labels = tf.zeros((batch_size, 1))\n",
        "\n",
        "    # Train the generator (note that we should *not* update the weights\n",
        "    # of the discriminator)!\n",
        "    with tf.GradientTape() as tape:\n",
        "        predictions = discriminator(generator(random_latent_vectors))\n",
        "        g_loss = loss_fn(misleading_labels, predictions)\n",
        "    grads = tape.gradient(g_loss, generator.trainable_weights)\n",
        "    g_optimizer.apply_gradients(zip(grads, generator.trainable_weights))\n",
        "    return d_loss, g_loss, generated_images\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "fa6bd6292488"
      },
      "source": [
        "일련의 이미지에 대해 `train_step` 을 반복해서 호출하여 GAN을 훈련시켜 봅시다.\n",
        "\n",
        "판별 기 및 생성기는 convnet이므로이 코드를 GPU에서 실행하려고합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "b6a4e3d42262"
      },
      "outputs": [],
      "source": [
        "import os\n",
        "\n",
        "# Prepare the dataset. We use both the training & test MNIST digits.\n",
        "batch_size = 64\n",
        "(x_train, _), (x_test, _) = keras.datasets.mnist.load_data()\n",
        "all_digits = np.concatenate([x_train, x_test])\n",
        "all_digits = all_digits.astype(\"float32\") / 255.0\n",
        "all_digits = np.reshape(all_digits, (-1, 28, 28, 1))\n",
        "dataset = tf.data.Dataset.from_tensor_slices(all_digits)\n",
        "dataset = dataset.shuffle(buffer_size=1024).batch(batch_size)\n",
        "\n",
        "epochs = 1  # In practice you need at least 20 epochs to generate nice digits.\n",
        "save_dir = \"./\"\n",
        "\n",
        "for epoch in range(epochs):\n",
        "    print(\"\\nStart epoch\", epoch)\n",
        "\n",
        "    for step, real_images in enumerate(dataset):\n",
        "        # Train the discriminator & generator on one batch of real images.\n",
        "        d_loss, g_loss, generated_images = train_step(real_images)\n",
        "\n",
        "        # Logging.\n",
        "        if step % 200 == 0:\n",
        "            # Print metrics\n",
        "            print(\"discriminator loss at step %d: %.2f\" % (step, d_loss))\n",
        "            print(\"adversarial loss at step %d: %.2f\" % (step, g_loss))\n",
        "\n",
        "            # Save one generated image\n",
        "            img = tf.keras.preprocessing.image.array_to_img(\n",
        "                generated_images[0] * 255.0, scale=False\n",
        "            )\n",
        "            img.save(os.path.join(save_dir, \"generated_img\" + str(step) + \".png\"))\n",
        "\n",
        "        # To limit execution time we stop after 10 steps.\n",
        "        # Remove the lines below to actually train the model!\n",
        "        if step > 10:\n",
        "            break"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "a92959ac630b"
      },
      "source": [
        "그게 다야! Colab GPU에 대해 ~ 30 번의 교육을받은 후에 멋진 가짜 MNIST 숫자를 얻을 수 있습니다."
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [],
      "name": "writing_a_training_loop_from_scratch.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
